<template>
    <div id="menu-bar">
        <h1>
            <img :src="logo" alt="logo" style="height: 48px;"/>
            Dreamwork <a @click="ui.showAbout=true" style="text-decoration: underline; text-underline-mode: thin">C.A.I.</a> Monitor
            <small>V 1.2.1</small>
        </h1>
        <div class="operation-area">
            <input class="ns-finder" type="text" placeholder="请输入namespace搜索" />
            <span class="split-10"></span>
            <div class="dropdown flexable">
                <a class="btn btn-secondary dropdown-toggle" data-toggle="dropdown"></a>
                <div class="dropdown-menu dropdown-menu-right">
                    <a class="dropdown-item" id="mi-change-theme" @click="ui.showThemeSelector = true">
                        <i class="fas fa-theater-masks colored-icon"></i><span class="split-10"></span>
                        Change Theme
                    </a>
                </div>
            </div>
        </div>
    </div>

    <transition-group tag="div" name="srf" class="flexable wrap p-20">
        <div class="srf-root" :data-namespace="srf.namespace" @dblclick="beforeSRFOpen (srf)"
             v-for="(srf, index) of frames" :key="index">
            <div class="srf-title">{{ srf.namespace }}</div>
            <div class="srf-title-meta">&lt;&lt;from: {{ srf.host }}:{{ srf.udpPort }}&gt;&gt;</div>
            <div class="srf-title-meta">&lt;&lt;created: {{ formatDateTime (srf.timestamp) }}&gt;&gt;</div>
            <div class="srf-title-meta" data-role="update-timestamp">&lt;&lt;updated: {{ formatDateTime (srf.lastUpdate) }}&gt;&gt;</div>
            <div class="srf-title-desc" v-if="srf.description">{{ srf.description }}</div>
        </div>
    </transition-group>

    <template v-if="ui.showAbout"><about v-model="ui.showAbout"/></template>
    <template v-if="detail.visible">
        <srf-detail v-model="detail.visible" :srf="detail.srf" />
    </template>
    <template v-if="ui.showThemeSelector">
        <theme-selector v-model="ui.showThemeSelector" @theme-changed="onThemeChanged"/>
    </template>

    <template v-if="ui.showResult">
        <customer-dialog v-model="ui.showResult" @closed="result = {}"
                         :show-footer="false" :hide-on-click-modal="false">
            <template #header>
                <i class="fa-regular space mr-10"
                   :class="{'fa-circle-check': result.success, 'text-success': result.success,
                            'fa-circle-xmark': !result.success, 'text-danger': !result.success}" />
                <div class="flexable auto-grow">Result</div>
            </template>
            <div class="description srf-dlg-desc p-10">{{ result.description }}</div>
            <div class="p-10">
                <div style="display: grid; grid-template-columns: max-content auto; grid-gap: 10px;">
                    <div>Return Type</div><div>{{ result.returnType }}</div>
                    <div>Signature</div><div>{{ result.signature }}</div>
                    <div>Arguments:</div>
                    <div class="scrollable bordered all pl-10"
                         style="display: grid; grid-template-columns: repeat(3, 1fr); max-height: 120px; grid-gap: 10px">
                        <template v-for="(arg, index) of result.args" :key="index">
                            <div>{{ arg.name }}</div>
                            <div>{{ getSimpleName (arg.type) }}</div>
                            <div v-html="arg.value" />
                        </template>
                    </div>
                </div>
            </div>
            <div style="min-width: 640px" class="pb-10 pv-10">
                <template v-if="result.stackTrace">
                    <div class="scrollable bordered all" style="max-height: 400px">
                        <pre class="text-danger">{{ result.stackTrace }}</pre>
                    </div>
                </template>
                <template v-if="result.returnType !== 'void'">
                    <div class="scrollable bordered all" style="max-height: 400px" v-if="result.success">
                        <pre>{{ result.value }}</pre>
                    </div>
                </template>
            </div>
        </customer-dialog>
    </template>
</template>
<script>
// import dataModel from "@/assets/data";

import logo from '@/assets/images/cai-logo.png';
import '@/assets/font-awesome/6.4.0/css/all.css';

import WebSocketWrapper from "@/utils/web-socket";
import {Bus} from "@/utils/metadata";

import $ from 'jquery';
import About from "@/components/about.vue";
import CustomerDialog from "@/components/customer-dialog.vue";
import ServiceRouterFrame from "@/components/service-router-frame.vue";
import SrfDetail from "@/components/srf-detail.vue";
import ThemeSelector from "@/components/theme-selector.vue";

import {
    buildResult,
    buildWebSocketURL,
    cancel_select,
    getSimpleName,
    isCollection,
    isSimpleType,
    patchItems
} from "@/utils/functions";
import NullableCheckbox from "@/components/nullable-checkbox.vue";

// eggs
import EasterEggsHook from "@/utils/easter-eggs-hook";
import {loadGame, showGame} from "@/game/game-hook";

const app = {};
let socket;
export default {
    name: 'Dreamwork Cross-Application-Invoke Monitor',
    components: {NullableCheckbox, About, CustomerDialog, ServiceRouterFrame, SrfDetail, ThemeSelector},
    data () {
        return {
            themeName: 'dark',
            logo,
            ui: {
                showAbout: false,
                showDialog: false,
                showThemeSelector: false,
                showResult: false
            },
            frames: [],
            detail: {visible: false, srf: {}},
            tester: undefined,
            result: {}
        };
    },
    methods: {
        getSimpleName,
        onThemeChanged (theme) {
            console.log ('current theme change to %s', theme);
            this.themeName = theme;
            this.$nextTick (() => {
                if (app.theme) {
                    app.theme.remove ();
                }
                app.theme = $('<link rel="stylesheet" type="text/css"/>')
                    .attr ('href', 'themes/monitor-' + this.themeName + '.css').appendTo ($(document.head));
                localStorage.setItem ('theme', this.themeName);
            });
        },
        beforeSRFOpen (srf) {
            this.detail.srf = srf;
            this.detail.visible = true;
        },
        formatDateTime (timestamp) {
            return new Date (timestamp).format ('yyyy-MM-dd HH:mm:ss');
        },
        // ws handlers
        onCreate (message) {
            // this.frames = message.data.frames;
            if (message.data.frames) {
                let target = [];
                Object.values (message.data.frames).forEach (srf => {
                    target.push (patchItems (srf));
                });
                target.sort ((a, b) => a.namespace > b.namespace ? 1 : -1)
                this.frames = target;
            }
        },
        onUpdate (srf) {
            console.log ('updating srf::', srf);
            let temp = [];
            this.frames.forEach (frame => temp.push (frame));
            temp.push (srf);
            temp.sort ((a, b) => a.namespace > b.namespace ? 1 : -1);
            console.log ('after update and sort, frames = ', temp);
            this.frames = temp;
        },
        onWithdrawn (srf) {
            console.log ('srf [%s] withdrawn', srf.namespace);
            let temp = [];
            for (let f of this.frames) {
                if (f.namespace !== srf.namespace) {
                    temp.push (f);
                }
            }
            this.frames = temp;
            // 如果删除的srf是正被打开的，也需要关闭对话框
            if (this.detail.srf && this.detail.srf.namespace === srf.namespace) {
                this.detail.srf = {};
                this.detail.visible = false;
            }
        },
        onHeartbeat (namespace) {
            console.log ('srf[%s] say hello to me!', namespace);
            console.log ('hello %s', namespace);
            for (let srf of this.frames) {
                if (srf.namespace === namespace) {
                    srf.lastUpdate = new Date ().getTime ();
                }
            }
        },
        //
        showResult ({invocation, res}) {
            console.log ('app::on::remote-called::', invocation, res);
            let result = {};
            for (let frame of this.frames) {
                if (frame.namespace === invocation.namespace) {
                    if (frame.items && invocation.service in frame.items) {
                        for (let method of frame.items [invocation.service].methods) {
                            if (invocation.method === method.name) {
                                result = buildResult (invocation, res, method);
                                break;
                            }
                        }
                    }
                }
            }
            console.log ("result = ", result);
            this.result = result;
            this.ui.showResult = true;
        }
    },
    mounted () {
        this.$nextTick (() => {
            $('.srf-root, .srf-title, .srf-title-meta, .srf-title-desc').on ('selectstart', cancel_select);
            // $(document).on ('selectstart', cancel_select);
            this.themeName = localStorage.getItem ('theme');
            if (!this.themeName) {
                this.themeName = 'dark';
            }
            app.theme = $('<link rel="stylesheet" type="text/css"/>')
                .attr ('href', 'themes/monitor-' + this.themeName + '.css').appendTo ($(document.head));
        });
        // this.frames = dataModel.data.frames;
        let url = buildWebSocketURL ();
        console.log (url);

        let self = this;
        socket = new WebSocketWrapper ({
            onopen () {
                console.log ('websocket connected.');
            },
            onmessage (message) {
                message = JSON.parse (message);
                console.log ('received message: ', message);
                switch (message.action) {
                    case 'create':
                        self.onCreate (message);
                        break;
                    case 'update':
                        self.onUpdate (message.data);
                        break;
                    case 'withdrawn':
                        self.onWithdrawn (message.data);
                        break;
                    case 'heartbeat':
                        self.onHeartbeat (message.data);
                        break;
                    default :
                        console.warn ('unknown command: ', message);
                }
            },
        }).connect (url);

        // games
        EasterEggsHook.add ({
            clickTimes: 5,
            callback () {
                let root = $('#game-panel');
                if (!root.length) {
                    loadGame ();
                }

                showGame ();
            }
        });
    },
    unmounted () {
        if (socket) {
            socket.disconnect ();
        }
    },
    created () {
        console.log ('app created. bind the message bus');
        Bus.on ('remote-called', this.showResult);
    }
};
</script>
